Amazon API Gateway を利用してHTTPメソッドを変換してみた
こんにちは、AWS事業本部の荒平(@0Air)です。
最近、一部のHTTPメソッドが使えないため違うメソッドに変換できないか、との相談を受けました。
様々な方式が挙げられますが、本エントリでは、API Gatewayを用いてHTTPメソッドの変換を実施してみたので紹介します。
構成図
今回作成した構成はこちらです。
クライアントはAPI Gatewayにアクセスし、ALBを通じてFargateへリクエストを投げます。
実施手順
(1) 下準備
リクエストを受け付けるためのコンテナを作成します。今回はFlask + ECS on Fargateの構成としました。
既にリクエスト送信先の環境がある場合はスキップしてください。
サンプルのファイル構成は以下の通りです。
- apps.py (Flaskアプリ)
- requirements.txt (pipインストール用)
- Dockerfile
apps.py
from flask import Flask, request, jsonify app = Flask(__name__) @app.route('/', methods=['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'HEAD', 'OPTIONS']) def handle_request(): return jsonify({ 'method': request.method, 'headers': dict(request.headers), 'body': request.get_json(silent=True) }), 200 if __name__ == '__main__': app.run(host='0.0.0.0', port=80)
requirements.txt
Flask==2.3.2
Dockerfile
FROM amd64/python:3.8-alpine WORKDIR /app COPY requirements.txt requirements.txt RUN pip install -r requirements.txt COPY . . CMD [ "python", "/app/apps.py" ]
Dockerイメージをビルドして、ECRへプッシュします。(以下はコマンド例)
aws ecr get-login-password --region ap-northeast-1 | docker login --username AWS --password-stdin xxxxxxxxxxxxx.dkr.ecr.ap-northeast-1.amazonaws.com docker build -t {tag-name} . --platform amd64 docker tag {tag-name}:latest xxxxxxxxxxxxx.dkr.ecr.ap-northeast-1.amazonaws.com/{repository-name}:latest docker push xxxxxxxxxxxxx.dkr.ecr.ap-northeast-1.amazonaws.com/{repository-name}:latest
次に、ECS on FargateをALB込みのサービスとして起動します。
ここでは詳細は割愛しますが、以下の記事を参考に、作成したDockerイメージを起動します。
ALB経由でサービスにアクセスできることを確認します。
ALBを経由しているため、X-Forwarded-For
にて自端末のIPが表示されます。また、HTTPメソッドは、Webブラウザからアクセスした場合はGETが表示されました。
curl -X PUT {target}
コマンドでPUTを投げてみると、しっかりPUTとして受け付けていることが分かります。
(2) API Gatewayの設定
AWSコンソールにて、API Gatewayを表示し、REST APIを[構築]します。
プロトコルは[REST]を選択し、[新しいAPI]にて適当なAPI名を入力します。
[APIの作成]から次の画面に進みます。
[アクション]から、[メソッドの作成]をクリックしてプルダウンから受け付けるHTTPメソッドを選択します。
今回の例では、POST
をPATCH
に変換するため、受け付けるHTTPメソッドとしてPOST
を選択しました。
統合タイプを[HTTP]として、HTTPメソッドには変換後(この例ではPATCH
)を選択します。
エンドポイントURLには、今回ALBのDNS名を入力しました。
(3) テストの実行
設定したメソッドをクリックすると、以下の概念図のような画面が表示されます。
[テスト]ボタンを押して、リクエストを発出してみます。
レスポンス内容とログが表示されます。
Flaskのサービスが"method":"PATCH"
と返していますので、PATCHとして認識されていることが分かりました。
(4) APIのデプロイ
動作が確認できたので、作成したAPIをデプロイします。
[アクション]から、[APIのデプロイ]をクリックします。
適当なステージに[デプロイ]します。今回は新規に"dev"ステージとしました。
デプロイすると呼び出し用のURLが払い出されるので、これをコピーします。
curl -X POST {target}
コマンドでPOSTを投げてみると、PATCHとして受け付けていることが分かり、正しい挙動であることが確認できました。
IPアドレス制限を実装する (Optional)
ここまでで紹介した手順は、ALBがインターネットに公開され(0.0.0.0/0)、機微なシステムには不向きでした。
API GatewayとALBの間にNLBを追加し、ALBのセキュリティグループを変更することで、IPアドレスの制限を実装します。
以下の記事が参考になります。
構成図 (NLB追加)
(1) NLBの作成
公式ドキュメントを参考に、プライベートサブネットへNetwork Load Balancerを作成します。
但し、登録するターゲットグループには利用するALBを登録します。
また、ALBのセキュリティグループはNLBのIPレンジを許可するように変更します。(検証では、インターネットからのアクセス 0.0.0.0/0許可も同時に削除)
(2) VPCリンクの作成
API Gatewayの左ペインから、VPCリンクを表示し、[Create]をクリックします。
[REST API の VPCリンク]を選択し、名前と作成したNLBを選択します。
VPCリンクの作成は2〜4分掛かります。
(3) APIの編集
続いて、既存APIの設定を変更します。
統合タイプは[VPCリンク]を指定し、作成したVPCリンクを選択します。
確認画面で[OK]をクリックし、APIをデプロイすると切り替わります。
例のごとく、curl -X POST {target}
コマンドでPOSTを投げてみると、PATCHとして受け付けていることが分かり、正しい挙動であることが確認できました。
また、X-Forwarded-ForはNLBのPrivate IPアドレスとなっています。
(4) API Gatewayへのアクセス制限
ここまでALBへのアクセス制限を行いましたが、API Gatewayには無制限でアクセスできる状態なので、リソースポリシーによる送信元IPアドレス制限を設定します。
以下の記事が参考になります。
リソースポリシー画面にて、例示されている[IP範囲の拒否リスト]をクリックすると、テンプレートが展開されます。
適宜アクセスを許可するIPアドレスなどを入力して、許可されないIPアドレスからのアクセスは無効にします。
検証では以下のようなポリシーを作成しました。
{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Principal": "*", "Action": "execute-api:Invoke", "Resource": "arn:aws:execute-api:ap-northeast-1:000000000000:{api-id}/*/*/*" }, { "Effect": "Deny", "Principal": "*", "Action": "execute-api:Invoke", "Resource": "arn:aws:execute-api:ap-northeast-1:000000000000:{api-id}/*/*/*", "Condition":{ "NotIpAddress": { "aws:SourceIp": [ "{myIPaddress}/32" ] } } } ] }
API Gatewayにアクセスする端末のIPアドレスにより制御できることが確認できました。
また、上記ドキュメントではソース VPC または VPC エンドポイントに基づいて制御する例も記載されています。
おわりに
API GatewayによるHTTPメソッドの変換を試してみました。
触ったことがない領域でしたが、簡単にリクエストを編集できるので非常に手軽だと感じました。
このエントリが誰かの助けになれば幸いです。
それでは、AWS事業本部 コンサルティング部の荒平(@0Air)がお送りしました!
参考
API Gatewayのスループットが気になる場合: